/**
 * Copyright (C) 2013-2014 the original author or authors.
 */
package com.foreveross.bsl.mongodb.repository;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.PatternSyntaxException;

import org.apache.commons.lang3.StringUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;

import com.foreveross.bsl.common.utils.bean.Collections3;
import com.foreveross.bsl.common.utils.web.QueryFilter;
import com.foreveross.bsl.common.utils.web.QueryOrder;
import com.foreveross.bsl.common.utils.web.QuerySpecs;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;


/**
 * 简单的MongoDB动态Query构建器，现只实现对SearchFilter集合的条件and连接
 *
 * @author Wangyi
 * @version v1.0
 *
 * @date 2013-9-10
 * @see QueryFilter
 */
public class DynamicQueryBuilder {
	
	private ConversionService conversionService;
	
	/**
	 * 
	 */
	public DynamicQueryBuilder() {
		DefaultConversionService def=new DefaultConversionService();
		def.addConverter(new StringToDateConverter());
		conversionService=def;
	}
	
	/**
	 * 根据SearchFilter集合生成mongoDB的Query对象,所有条件都是用and逻辑连接
	 * @param filters
	 * @return
	 */
	public Query byQuerySpecs(final QuerySpecs querySpecs){
		Query query=new Query();
		Criteria cri=this.buildCriteria(querySpecs.getFilters());
		if(cri!=null){
			query.addCriteria(cri);
		}
		Sort sort=this.buildSort(querySpecs.getOrders());
		if(sort!=null){
			query.with(sort);
		}
		return query;
	}
	
	/**
	 * 过虑条件中的值将根据entityClass的属性的类型转换相应的值
	 * @param querySpecs
	 * @param entityClass
	 * @return
	 */
	public Query byQuerySpecs(final QuerySpecs querySpecs, Class<?> entityClass){
		final Collection<QueryFilter> filters=deepCopyQueryFilters(querySpecs.getFilters());
		ReflectionUtils.doWithFields(entityClass, new FieldCallback(){
			@Override
			public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
				String fieldName=field.getName();
				org.springframework.data.mongodb.core.mapping.Field fieldAnn=
						field.getAnnotation(org.springframework.data.mongodb.core.mapping.Field.class);
				String annName=fieldName;
				if(fieldAnn!=null && StringUtils.isNotBlank(fieldAnn.value())){
					//@Field注释声明的字段名
					annName=fieldAnn.value();
				}
				String rn=null, fn=null;
				for(QueryFilter filter : filters){
					rn=null;
					fn=filter.getFieldName();
					if(annName.equals(fn) || fieldName.equals(fn)){
						rn=annName;
					}
					if(rn!=null){
						Object targetValue=convertTextToRealType(filter.getValue(), field.getType());
						querySpecs.replaceFilter(filter, 
								new QueryFilter(rn, filter.getOperator(), targetValue));
					}
				}
			}
		});
		return byQuerySpecs(querySpecs);
	}
	
	private Collection<QueryFilter> deepCopyQueryFilters(Collection<QueryFilter> filters){
		Collection<QueryFilter> clones=Lists.newArrayListWithExpectedSize(filters.size());
		for(QueryFilter qf : filters){
			clones.add(new QueryFilter(qf.getFieldName(), qf.getOperator(), qf.getValue()));
		}
		return clones;
	}
	
	/**
	 * 将文本转换成期望的类型
	 * @param value
	 * @param targetType
	 * @return
	 */
	private Object convertTextToRealType(Object value, Class<?> targetType){
		if(!(value instanceof String) || 
				((value instanceof String) && String.class.isAssignableFrom(targetType))){
			return value;
		}
		String text=(String)value;
		if(conversionService.canConvert(String.class, targetType)){
			return this.conversionService.convert(text, targetType);
		}
		return value;
	}
	
	private Criteria buildCriteria(Collection<QueryFilter> filters){
		if(Collections3.isEmpty(filters)){
			return null;
		}
		Criteria cri=new Criteria(), t=null;
		Map<String, List<QueryFilter>> groups=this.groupQueryFilters(filters);
		for(Map.Entry<String, List<QueryFilter>> entry : groups.entrySet()){
			t=cri.and(entry.getKey());
			populateCriteria(t, entry.getValue());
		}
		return cri;
	}
	
	private Map<String, List<QueryFilter>> groupQueryFilters(Collection<QueryFilter> filters){
		Map<String, List<QueryFilter>> groups=Maps.newHashMap();
		for(QueryFilter filter : filters){
			List<QueryFilter> qfList=groups.get(filter.getFieldName());
			if(qfList==null){
				qfList=Lists.newArrayList(filter);
				groups.put(filter.getFieldName(), qfList);
			}
			else{
				qfList.add(filter);
			}
		}
		return groups;
	}
	
	private Sort buildSort(List<QueryOrder> orders){
		if(Collections3.isEmpty(orders)){
			return null;
		}
		List<Sort.Order> distOrders=new ArrayList<Sort.Order>(orders.size());
		for(QueryOrder qo : orders){
			distOrders.add(new Sort.Order(Sort.Direction.valueOf(qo.getDirection().name()), qo.getProperty()));
		}
		return new Sort(distOrders);
	}
	
	private static Criteria populateCriteria(Criteria cri, Collection<QueryFilter> filters){
		for(QueryFilter filter : filters){
			QueryFilter.Operator op=filter.getOperator();
			Object value=filter.getValue();
			if(value instanceof String){
				value = ((String)value).trim();
			}
			switch (op) {
			case EQ:
				cri.is(value);
				break;
			case LIKE:
				try {
					//regex方法接收的是个正则表达式字符串(这说明regex方法不只实现like查询)，客户端的查询值中有可能输入一个含正则表达式的字符串
					cri.regex(escapeRegexString(value.toString()), "i");	//'i'表示不区分大小写
				} catch (PatternSyntaxException e) {
					//忽略escapeRegexString方法可能漏掉的正则表达式特殊字符导致的这个异常
				}
				break;
			case GT:
				cri.gt(value);
				break;
			case LT:
				cri.lt(value);
				break;
			case GTE:
				cri.gte(value);
				break;
			case LTE:
				cri.lte(value);
				break;
			}
		}
		return cri;
	}
	
	/**
	 * 把字符串中含有正则表达式特殊意义的字符转义
	 * @param value
	 * @return
	 */
	public static String escapeRegexString(String value){
		final char[] reserved={'.','$','^','{','}','[',']','(',')','|','*','+','?','\\'};
		char c;
		boolean bingo=false;
		StringBuilder sb=new StringBuilder(value);
		for(int i=0; i<sb.length(); i++){
			bingo=false;
			c=sb.charAt(i);
			for(int j=0; j<reserved.length; j++){
				if(c==reserved[j]){
					bingo=true;
					break;
				}
			}
			if(bingo){
				sb.insert(i, '\\');
				i++;
			}
		}
		return sb.toString();
	}
}
